{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "W_tvPdyfA-BL"
      },
      "source": [
        "##### Copyright 2018 The TensorFlow Authors."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "0O_LFhwSBCjm"
      },
      "outputs": [],
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "PWUmcKKjtwXL"
      },
      "source": [
        "# Hub with Keras\n",
        "\n",
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs/blob/master/site/en/r1/tutorials/images/hub_with_keras.ipynb\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\" />Run in Google Colab</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://github.com/tensorflow/docs/blob/master/site/en/r1/tutorials/images/hub_with_keras.ipynb\"><img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\" />View source on GitHub</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a href=\"https://tfhub.dev/s?module-type=image-classification\"><img src=\"https://www.tensorflow.org/images/hub_logo_32px.png\" />See TF Hub model</a>\n",
        "  </td>\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "PWUmcKKjtwXL"
      },
      "source": [
        "> Note: This is an archived TF1 notebook. These are configured\n",
        "to run in TF2's \n",
        "[compatibility mode](https://www.tensorflow.org/guide/migrate)\n",
        "but will run in TF1 as well. To use TF1 in Colab, use the\n",
        "[%tensorflow_version 1.x](https://colab.research.google.com/notebooks/tensorflow_version.ipynb)\n",
        "magic."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "crU-iluJIEzw"
      },
      "source": [
        "[TensorFlow Hub](http://tensorflow.org/hub) is a way to share pretrained model components. See the [TensorFlow Module Hub](https://tfhub.dev/) for a searchable listing of pre-trained models.\n",
        "\n",
        "This tutorial demonstrates:\n",
        "\n",
        "1. How to use TensorFlow Hub with `tf.keras`.\n",
        "1. How to do image classification using TensorFlow Hub.\n",
        "1. How to do simple transfer learning."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "CKFUvuEho9Th"
      },
      "source": [
        "## Setup"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "7RVsYZLEpEWs"
      },
      "source": [
        "### Imports"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "nSiOCtv_Rwi_"
      },
      "outputs": [],
      "source": [
        "!pip install -U tensorflow_hub"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "OGNpmn43C0O6"
      },
      "outputs": [],
      "source": [
        "import matplotlib.pylab as plt\n",
        "\n",
        "import tensorflow.compat.v1 as tf\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "-V4l8oN8Lw2q"
      },
      "outputs": [],
      "source": [
        "import os\n",
        "import tensorflow_hub as hub\n",
        "from tensorflow.keras import layers\n",
        "\n",
        "# Load compressed models from tensorflow_hub\n",
        "os.environ[\"TFHUB_MODEL_LOAD_FORMAT\"] = \"COMPRESSED\""
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "s4YuF5HvpM1W"
      },
      "source": [
        "## An ImageNet classifier"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "xEY_Ow5loN6q"
      },
      "source": [
        "### Download the classifier\n",
        "\n",
        "Use `hub.module` to load a mobilenet, and `tf.keras.layers.Lambda` to wrap it up as a keras layer.\n",
        "\n",
        "The URL of any [TF2-compatible image classification module](https://tfhub.dev/s?module-type=image-classification&q=tf2) from tfhub.dev will work here."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "both",
        "id": "feiXojVXAbI9"
      },
      "outputs": [],
      "source": [
        "classifier_url =\"https://tfhub.dev/google/tf2-preview/mobilenet_v2/classification/2\" #@param {type:\"string\"}"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "y_6bGjoPtzau"
      },
      "outputs": [],
      "source": [
        "IMAGE_SHAPE = (224, 224)\n",
        "\n",
        "classifier = tf.keras.Sequential([\n",
        "    hub.KerasLayer(classifier_url, input_shape=IMAGE_SHAPE+(3,))\n",
        "])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "pwZXaoV0uXp2"
      },
      "source": [
        "### Run it on a single image"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "TQItP1i55-di"
      },
      "source": [
        "Download a single image to try the model on."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "w5wDjXNjuXGD"
      },
      "outputs": [],
      "source": [
        "import numpy as np\n",
        "import PIL.Image as Image\n",
        "\n",
        "grace_hopper = tf.keras.utils.get_file('image.jpg','https://storage.googleapis.com/download.tensorflow.org/example_images/grace_hopper.jpg')\n",
        "grace_hopper = Image.open(grace_hopper).resize(IMAGE_SHAPE)\n",
        "grace_hopper"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "BEmmBnGbLxPp"
      },
      "outputs": [],
      "source": [
        "grace_hopper = np.array(grace_hopper)/255.0\n",
        "grace_hopper.shape"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "0Ic8OEEo2b73"
      },
      "source": [
        "Add a batch dimension, and pass the image to the model."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "EMquyn29v8q3"
      },
      "outputs": [],
      "source": [
        "result = classifier.predict(grace_hopper[np.newaxis, ...])\n",
        "result.shape"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "NKzjqENF6jDF"
      },
      "source": [
        "The result is a 1001 element vector of logits, rating the probability of each class for the image.\n",
        "\n",
        "So the top class ID can be found with argmax:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "rgXb44vt6goJ"
      },
      "outputs": [],
      "source": [
        "predicted_class = np.argmax(result[0], axis=-1)\n",
        "predicted_class"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "YrxLMajMoxkf"
      },
      "source": [
        "### Decode the predictions\n",
        "\n",
        "We have the predicted class ID,\n",
        "Fetch the `ImageNet` labels, and decode the predictions"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ij6SrDxcxzry"
      },
      "outputs": [],
      "source": [
        "labels_path = tf.keras.utils.get_file('ImageNetLabels.txt','https://storage.googleapis.com/download.tensorflow.org/data/ImageNetLabels.txt')\n",
        "imagenet_labels = np.array(open(labels_path).read().splitlines())"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "uzziRK3Z2VQo"
      },
      "outputs": [],
      "source": [
        "plt.imshow(grace_hopper)\n",
        "plt.axis('off')\n",
        "predicted_class_name = imagenet_labels[predicted_class]\n",
        "_ = plt.title(\"Prediction: \" + predicted_class_name.title())"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "amfzqn1Oo7Om"
      },
      "source": [
        "## Simple transfer learning"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "K-nIpVJ94xrw"
      },
      "source": [
        "Using TF Hub it is simple to retrain the top layer of the model to recognize the classes in our dataset."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Z93vvAdGxDMD"
      },
      "source": [
        "### Dataset\n",
        "\n",
        " For this example you will use the TensorFlow flowers dataset:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "DrIUV3V0xDL_"
      },
      "outputs": [],
      "source": [
        "data_root = tf.keras.utils.get_file(\n",
        "  'flower_photos','https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz',\n",
        "   untar=True)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "jFHdp18ccah7"
      },
      "source": [
        "The simplest way to load this data into our model is using `tf.keras.preprocessing.image.ImageDataGenerator`,\n",
        "\n",
        "All of TensorFlow Hub's image modules expect float inputs in the `[0, 1]` range. Use the `ImageDataGenerator`'s `rescale` parameter to achieve this.\n",
        "\n",
        "The image size will be handled later."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "2PwQ_wYDcii9"
      },
      "outputs": [],
      "source": [
        "image_generator = tf.keras.preprocessing.image.ImageDataGenerator(rescale=1/255)\n",
        "image_data = image_generator.flow_from_directory(str(data_root), target_size=IMAGE_SHAPE)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "0p7iDOhIcqY2"
      },
      "source": [
        "The resulting object is an iterator that returns `image_batch, label_batch` pairs."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "W4lDPkn2cpWZ"
      },
      "outputs": [],
      "source": [
        "for image_batch, label_batch in image_data:\n",
        "  print(\"Image batch shape: \", image_batch.shape)\n",
        "  print(\"Label batch shape: \", label_batch.shape)\n",
        "  break"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "0gTN7M_GxDLx"
      },
      "source": [
        "### Run the classifier on a batch of images"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "O3fvrZR8xDLv"
      },
      "source": [
        "Now run the classifier on the image batch."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "nbyg6tcyxDLh"
      },
      "outputs": [],
      "source": [
        "result_batch = classifier.predict(image_batch)\n",
        "result_batch.shape"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Kv7ZwuR4xDLc"
      },
      "outputs": [],
      "source": [
        "predicted_class_names = imagenet_labels[np.argmax(result_batch, axis=-1)]\n",
        "predicted_class_names"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "QmvSWg9nxDLa"
      },
      "source": [
        "Now check how these predictions line up with the images:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "IXTB22SpxDLP"
      },
      "outputs": [],
      "source": [
        "plt.figure(figsize=(10,9))\n",
        "plt.subplots_adjust(hspace=0.5)\n",
        "for n in range(30):\n",
        "  plt.subplot(6,5,n+1)\n",
        "  plt.imshow(image_batch[n])\n",
        "  plt.title(predicted_class_names[n])\n",
        "  plt.axis('off')\n",
        "_ = plt.suptitle(\"ImageNet predictions\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "FUa3YkvhxDLM"
      },
      "source": [
        "See the `LICENSE.txt` file for image attributions.\n",
        "\n",
        "The results are far from perfect, but reasonable considering that these are not the classes the model was trained for (except \"daisy\")."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "JzV457OXreQP"
      },
      "source": [
        "### Download the headless model\n",
        "\n",
        "TensorFlow Hub also distributes models without the top classification layer. These can be used to easily do transfer learning.\n",
        "\n",
        "The URL of any [TF2-compatible image feature vector module](https://tfhub.dev/s?module-type=image-feature-vector&q=tf2) from tfhub.dev will work here."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "both",
        "id": "4bw8Jf94DSnP"
      },
      "outputs": [],
      "source": [
        "feature_extractor_url = \"https://tfhub.dev/google/tf2-preview/mobilenet_v2/feature_vector/2\" #@param {type:\"string\"}"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "sgwmHugQF-PD"
      },
      "source": [
        "Create the module, and check the expected image size:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "5wB030nezBwI"
      },
      "outputs": [],
      "source": [
        "feature_extractor_layer = hub.KerasLayer(feature_extractor_url,\n",
        "                                         input_shape=(224,224,3))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "GUY-5Eyzuzlu"
      },
      "source": [
        "The feature extractor returns a 1280-element vector for each image."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Of7i-35F09ls"
      },
      "outputs": [],
      "source": [
        "feature_batch = feature_extractor_layer(image_batch)\n",
        "print(feature_batch.shape)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "CtFmF7A5E4tk"
      },
      "source": [
        "Freeze the variables in the feature extractor layer, so that the training only modifies the new classifier layer."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Jg5ar6rcE4H-"
      },
      "outputs": [],
      "source": [
        "feature_extractor_layer.trainable = False"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "RPVeouTksO9q"
      },
      "source": [
        "### Attach a classification head\n",
        "\n",
        "Now wrap the hub layer in a `tf.keras.Sequential` model, and add a new classification layer."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "mGcY27fY1q3Q"
      },
      "outputs": [],
      "source": [
        "model = tf.keras.Sequential([\n",
        "  feature_extractor_layer,\n",
        "  layers.Dense(image_data.num_classes, activation='softmax')\n",
        "])\n",
        "\n",
        "model.summary()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "G9VkAz00HOJx"
      },
      "outputs": [],
      "source": [
        "predictions = model(image_batch)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "sB7sVGJ23vrY"
      },
      "outputs": [],
      "source": [
        "predictions.shape"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "OHbXQqIquFxQ"
      },
      "source": [
        "### Train the model\n",
        "\n",
        "Use compile to configure the training process:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "3n0Wb9ylKd8R"
      },
      "outputs": [],
      "source": [
        "model.compile(\n",
        "  optimizer=tf.keras.optimizers.Adam(),\n",
        "  loss='categorical_crossentropy',\n",
        "  metrics=['acc'])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "58-BLV7dupJA"
      },
      "source": [
        "Now use the `.fit` method to train the model.\n",
        "\n",
        "To keep this example short train just 2 epochs. To visualize the training progress, use a custom callback to log the loss and accuracy of each batch individually, instead of the epoch average."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "jZ54Gubac4Lu"
      },
      "outputs": [],
      "source": [
        "class CollectBatchStats(tf.keras.callbacks.Callback):\n",
        "  def __init__(self):\n",
        "    self.batch_losses = []\n",
        "    self.batch_acc = []\n",
        "\n",
        "  def on_train_batch_end(self, batch, logs=None):\n",
        "    self.batch_losses.append(logs['loss'])\n",
        "    self.batch_acc.append(logs['acc'])\n",
        "    self.model.reset_metrics()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "EyMDJxt2HdHr"
      },
      "outputs": [],
      "source": [
        "steps_per_epoch = np.ceil(image_data.samples/image_data.batch_size)\n",
        "\n",
        "batch_stats_callback = CollectBatchStats()\n",
        "\n",
        "history = model.fit(image_data, epochs=2,\n",
        "                    steps_per_epoch=steps_per_epoch,\n",
        "                    callbacks = [batch_stats_callback])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Kd0N272B9Q0b"
      },
      "source": [
        "Now after, even just a few training iterations, we can already see that the model is making progress on the task."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "A5RfS1QIIP-P"
      },
      "outputs": [],
      "source": [
        "plt.figure()\n",
        "plt.ylabel(\"Loss\")\n",
        "plt.xlabel(\"Training Steps\")\n",
        "plt.ylim([0,2])\n",
        "plt.plot(batch_stats_callback.batch_losses)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "3uvX11avTiDg"
      },
      "outputs": [],
      "source": [
        "plt.figure()\n",
        "plt.ylabel(\"Accuracy\")\n",
        "plt.xlabel(\"Training Steps\")\n",
        "plt.ylim([0,1])\n",
        "plt.plot(batch_stats_callback.batch_acc)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "kb__ZN8uFn-D"
      },
      "source": [
        "### Check the predictions\n",
        "\n",
        "To redo the plot from before, first get the ordered list of class names:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "JGbEf5l1I4jz"
      },
      "outputs": [],
      "source": [
        "class_names = sorted(image_data.class_indices.items(), key=lambda pair:pair[1])\n",
        "class_names = np.array([key.title() for key, value in class_names])\n",
        "class_names"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "4Olg6MsNGJTL"
      },
      "source": [
        "Run the image batch through the model and convert the indices to class names."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "fCLVCpEjJ_VP"
      },
      "outputs": [],
      "source": [
        "predicted_batch = model.predict(image_batch)\n",
        "predicted_id = np.argmax(predicted_batch, axis=-1)\n",
        "predicted_label_batch = class_names[predicted_id]"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "CkGbZxl9GZs-"
      },
      "source": [
        "Plot the result"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "rpFQR1MPMtT1"
      },
      "outputs": [],
      "source": [
        "label_id = np.argmax(label_batch, axis=-1)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "wC_AYRJU9NQe"
      },
      "outputs": [],
      "source": [
        "plt.figure(figsize=(10,9))\n",
        "plt.subplots_adjust(hspace=0.5)\n",
        "for n in range(30):\n",
        "  plt.subplot(6,5,n+1)\n",
        "  plt.imshow(image_batch[n])\n",
        "  color = \"green\" if predicted_id[n] == label_id[n] else \"red\"\n",
        "  plt.title(predicted_label_batch[n].title(), color=color)\n",
        "  plt.axis('off')\n",
        "_ = plt.suptitle(\"Model predictions (green: correct, red: incorrect)\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "uRcJnAABr22x"
      },
      "source": [
        "## Export your model\n",
        "\n",
        "Now that you've trained the model, export it as a saved model:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "PLcqg-RmsLno"
      },
      "outputs": [],
      "source": [
        "import time\n",
        "t = time.time()\n",
        "\n",
        "export_path = \"/tmp/saved_models/{}\".format(int(t))\n",
        "model.save(export_path)\n",
        "\n",
        "export_path"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "AhQ9liIUsPsi"
      },
      "source": [
        "Now confirm that we can reload it, and it still gives the same results:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "7nI5fvkAQvbS"
      },
      "outputs": [],
      "source": [
        "reloaded = tf.keras.models.load_model(export_path, custom_objects={'KerasLayer':hub.KerasLayer})"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "jor83-LqI8xW"
      },
      "outputs": [],
      "source": [
        "result_batch = model.predict(image_batch)\n",
        "reloaded_result_batch = reloaded.predict(image_batch)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "dnZO14taYPH6"
      },
      "outputs": [],
      "source": [
        "abs(reloaded_result_batch - result_batch).max()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "TYZd4MNiV3Rc"
      },
      "source": [
        "This saved model can be loaded for inference later, or converted to [TFLite](https://www.tensorflow.org/lite/convert/) or [TFjs](https://github.com/tensorflow/tfjs-converter).\n"
      ]
    }
  ],
  "metadata": {
    "accelerator": "GPU",
    "colab": {
      "collapsed_sections": [
        "W_tvPdyfA-BL"
      ],
      "name": "hub_with_keras.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
